/*
 * Galaxium Messenger
 * Copyright (C) 2008 Paul Burton <paulburton89@gmail.com>
 * 
 * License: GNU General Public License (GPL)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

using System;
using System.Collections.Generic;

using Anculus.Core;

using Galaxium.Core;

namespace Galaxium.Protocol.Msn
{
	public abstract class AbstractMsnP2PBridge : IMsnP2PBridge
	{
		public event EventHandler BridgeOpened;
		public event EventHandler BridgeClosed;
		public event EventHandler<P2PMessageEventArgs> BridgeSent;
		
		static int _bridgeCount = 0;
		protected int _bridgeID = _bridgeCount++;
		
		protected int _queueSize;
		protected Dictionary<MsnP2PSession, MsnP2PSendQueue> _sendQueues;
		protected Dictionary<MsnP2PSession, MsnP2PSendList> _sendingQueues;
		protected List<MsnP2PSession> _stoppedSessions;
		
		public abstract bool Open { get; }
		public abstract int MaxDataSize { get; }
		
		public abstract MsnContact Remote { get; }
		
		public virtual Dictionary<MsnP2PSession, MsnP2PSendQueue> SendQueues
		{
			get { return _sendQueues; }
		}
		
		protected AbstractMsnP2PBridge (int queueSize)
		{
			Log.Debug ("P2PBridge {0} created", this);
			
			_queueSize = queueSize;
			_sendQueues = new Dictionary<MsnP2PSession, MsnP2PSendQueue> ();
			_sendingQueues = new Dictionary<MsnP2PSession, MsnP2PSendList> ();
			_stoppedSessions = new List<MsnP2PSession> ();
		}
		
		public virtual void Dispose ()
		{
			Log.Debug ("P2PBridge {0} disposed", this);
			
			_sendQueues.Clear ();
			_sendingQueues.Clear ();
			_stoppedSessions.Clear ();
		}
		
		// Checks if the MsnP2PSessions remote contact is the same as an existing
		// remote contact for this bridge
		public virtual bool SuitableFor (MsnP2PSession session)
		{
			MsnContact remote = Remote;
			
			return (session != null) && (remote != null) && (session.Remote == remote);
		}
		
		public virtual bool Ready (MsnP2PSession session)
		{
			if (_queueSize == 0)
				return Open && (!_stoppedSessions.Contains (session));
			
			if (!_sendingQueues.ContainsKey (session))
				return Open && SuitableFor (session) && (!_stoppedSessions.Contains (session));
			
			return Open && (_sendingQueues[session].Count < _queueSize) && (!_stoppedSessions.Contains (session));
		}
		
		public virtual void Send (MsnP2PSession session, MsnContact remote, P2PMessage msg)
		{
			ThrowUtility.ThrowIfNull ("remote", remote);
			
			//Log.Debug ("P2PBridge {0} sending message for {1}", this, (session != null) ? session.SessionID.ToString () : "null");
			
			P2PMessage[] msgs = MsnP2PUtility.SplitMessage (msg, MaxDataSize);
			
			if (session == null)
			{
				if (!Open)
				{
					Log.Error ("Send called with no session on a closed bridge");
					return;
				}
				
				// Bypass queueing
				
				foreach (P2PMessage m in msgs)
					TrueSend (null, remote, m);
				
				return;
			}
			
			if (!SuitableFor (session))
			{
				Log.Error ("Send called with a session this bridge is not suitable for");
				return;
			}
			
			if (!_sendQueues.ContainsKey (session))
				_sendQueues[session] = new MsnP2PSendQueue ();
			
			foreach (P2PMessage m in msgs)
				_sendQueues[session].Enqueue (remote, m);
			
			ProcessSendQueues ();
		}
		
		protected virtual void ProcessSendQueues ()
		{
			foreach (KeyValuePair<MsnP2PSession, MsnP2PSendQueue> pair in _sendQueues)
			{
				while (Ready (pair.Key) && (pair.Value.Count > 0))
				{
					MsnP2PSendItem item = pair.Value.Dequeue ();
					
					if (!_sendingQueues.ContainsKey (pair.Key))
						_sendingQueues.Add (pair.Key, new MsnP2PSendList ());
					
					_sendingQueues[pair.Key].Add (item);
					
					TrueSend (pair.Key, item.Remote, item.Message);
				}
			}
			
			bool moreQueued = false;
			foreach (KeyValuePair<MsnP2PSession, MsnP2PSendQueue> pair in _sendQueues)
			{
				if (pair.Value.Count > 0)
				{
					moreQueued = true;
					Log.Debug ("Queue holds {0} messages for session {1}", pair.Value.Count, pair.Key.SessionID);
				}
			}
			
			if (!moreQueued)
				Log.Debug ("Queues are all empty");
		}
		
		protected abstract void TrueSend (MsnP2PSession session, MsnContact remote, P2PMessage msg);
		
		public virtual void StopSending (MsnP2PSession session)
		{
			Log.Debug ("P2PBridge {0} stop sending for {0}", this, session.SessionID);
			
			if (!_stoppedSessions.Contains (session))
				_stoppedSessions.Add (session);
			else
				Log.Warn ("Session is already in stopped list");
		}
		
		public virtual void ResumeSending (MsnP2PSession session)
		{
			Log.Debug ("P2PBridge {0} resume sending for {0}", this, session.SessionID);
			
			if (_stoppedSessions.Contains (session))
			{
				_stoppedSessions.Remove (session);
				
				ProcessSendQueues ();
			}
			else
				Log.Warn ("Session not present in stopped list");
		}
		
		public virtual void MigrateQueue (MsnP2PSession session, IMsnP2PBridge newBridge)
		{
			Log.Debug ("P2PBridge {0} migrating session {1} queue to new bridge {2}",
			           this, session.SessionID, (newBridge != null) ? newBridge.ToString () : "null");
			
			MsnP2PSendQueue newQueue = new MsnP2PSendQueue ();
			
			if (_sendingQueues.ContainsKey (session))
			{
				if (newBridge != null)
				{
					foreach (MsnP2PSendItem item in _sendingQueues[session])
						newQueue.Enqueue (item);
				}
				
				_sendingQueues.Remove (session);
			}
			
			if (_sendQueues.ContainsKey (session))
			{
				if (newBridge != null)
				{
					while (_sendQueues[session].Count > 0)
						newQueue.Enqueue (_sendQueues[session].Dequeue ());
				}
				
				_sendQueues.Remove (session);
			}
			
			if (_stoppedSessions.Contains (session))
				_stoppedSessions.Remove (session);
			
			if (newBridge != null)
				newBridge.AddQueue (session, newQueue);
		}
		
		public virtual void AddQueue (MsnP2PSession session, MsnP2PSendQueue queue)
		{
			Log.Debug ("P2PBridge {0} received queue for session {1}", this, session.SessionID);
			
			if (_sendQueues.ContainsKey (session))
			{
				Log.Debug ("A queue is already present for this session, merging the queues");
				
				while (queue.Count > 0)
					_sendQueues[session].Enqueue (queue.Dequeue ());
			}
			else
				_sendQueues[session] = queue;
			
			ProcessSendQueues ();
		}
		
		protected virtual void OnBridgeOpened ()
		{
			Log.Debug ("P2PBridge {0} opened", this);
			
			if (BridgeOpened != null)
				BridgeOpened (this, EventArgs.Empty);
			
			ProcessSendQueues ();
		}
		
		protected virtual void OnBridgeClosed ()
		{
			Log.Debug ("P2PBridge {0} closed", this);
			
			if (BridgeClosed != null)
				BridgeClosed (this, EventArgs.Empty);
		}
		
		protected virtual void OnBridgeSent (P2PMessageEventArgs args)
		{
			if ((args.Session != null) && _sendingQueues.ContainsKey (args.Session))
			{
				if (_sendingQueues[args.Session].Contains (args.Message))
					_sendingQueues[args.Session].Remove (args.Message);
				else
				{
					Log.Error ("Sent message not present in sending queue");
				}
			}
			
			if (BridgeSent != null)
				BridgeSent (this, args);
			
			ProcessSendQueues ();
		}
		
		public override string ToString ()
		{
			return string.Format ("{0}:{1}", _bridgeID, GetType ().Name);
		}
	}
}
